<?php

namespace app\command\workerman;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\Container;
use think\facade\App;
use Workerman\Connection\TcpConnection;
use Workerman\Worker;

class Start extends Command
{
	protected function configure()
	{
		$this->setName('workerman')
			->addArgument('action', Argument::OPTIONAL, 'start|stop|restart|reload|status|connections', 'start')
			->addOption('daemon', 'd', Option::VALUE_NONE, 'daemon method run --daemon -d')
			->setDescription('Workerman Service');
	}

	protected function execute(Input $input, Output $output)
	{
		global $argv;
		array_shift($argv);
		array_unshift($argv, 'think', $input->getArgument('action'));

		$disableFunctions    = [
			'stream_socket_server',
			'stream_socket_client',
			'pcntl_signal_dispatch',
			'pcntl_signal',
			'pcntl_alarm',
			'pcntl_fork',
			'posix_getuid',
			'posix_getpwuid',
			'posix_kill',
			'posix_setsid',
			'posix_getpid',
			'posix_getpwnam',
			'posix_getgrnam',
			'posix_getgid',
			'posix_setgid',
			'posix_initgroups',
			'posix_setuid',
			'posix_isatty',
			'pcntl_wait'
		];
		$phpDisableFunctions = explode(',', ini_get('disable_functions'));
		foreach ($disableFunctions as $item) {
			if (in_array($item, $phpDisableFunctions, true)) {
				throw new \Exception('function [' . $item . '] not enabled, workerman failed to successfully start');
			}
		}

		$dir = App::getRuntimePath() . 'workerman';
		if (!is_dir($dir)) mkdir($dir);

		Worker::$pidFile    = $dir . '/think.pid';
		Worker::$logFile    = $dir . '/run.log';
		Worker::$stdoutFile = $dir . '/stdoutFile.log';
		if (property_exists(Worker::class, 'statusFile')) {
			Worker::$statusFile = $dir . '/status.log';
		}

		foreach (include 'process.php' as $processName => $config) {
			$this->workerStart($processName, $config);
		}

		Worker::runAll();
	}

	private function workerStart($processName, $config)
	{
		$worker = new Worker($config['listen'] ?? null, $config['context'] ?? []);

		$worker->name = $processName;

		$propertyMap = [
			'count',
			'user',
			'group',
			'reloadable',
			'reusePort',
			'transport',
			'protocol'
		];
		foreach ($propertyMap as $property) {
			if (isset($config[$property])) {
				$worker->$property = $config[$property];
			}
		}

		$staticMap = [
			'stopTimeout'
		];
		foreach ($staticMap as $item) {
			if (isset($config[$item]) && property_exists(Worker::class, $item)) {
				Worker::${$item} = $config[$item];
			}
		}

		$tcpMap = [
			'defaultMaxPackageSize'
		];
		foreach ($tcpMap as $property) {
			if (isset($config[$property])) {
				TcpConnection::${$property} = $config[$property];
			}
		}

		$worker->onWorkerStart = function ($worker) use ($config) {

			if (isset($config['handler'])) {
				if (!class_exists($config['handler'])) {
					dump("process error: class {$config['handler']} not exists");
					return;
				}

				$classInstance = Container::getInstance()->make($config['handler'], $config['construct'] ?? []);

				$this->workermanCallback($worker, $classInstance);
			}

		};
	}

	private function workermanCallback($worker, $classInstance)
	{
		$eventMap = [
			'onConnect',
			'onMessage',
			'onClose',
			'onError',
			'onBufferFull',
			'onBufferDrain',
			'onWorkerStop',
			'onWebSocketConnect',
			'onWorkerReload'
		];
		foreach ($eventMap as $event) {
			if (method_exists($classInstance, $event)) {
				$worker->$event = [$classInstance, $event];
			}
		}

		if (method_exists($classInstance, 'onWorkerStart')) {
			call_user_func([$classInstance, 'onWorkerStart'], $worker);
		}
	}
}